Chapter 22
Exceptions

by Keith McIntyre

In This Chapter

  Exceptions—What Are They Good For? 782
  Types of Exceptions 785
  Structured Exception Handlers 786
  Nesting of Structured Exception Handlers 788
  Raising Structured Exceptions 789
  Cleaning Up After an Exception 790
  C++ Exceptions 792
  Defining a C++ Exception Class 795
  MFC Exceptions 797
  Deriving Your Own MFC-Compliant Exception Objects 808
  Deleting Exceptions 810
  Using MFC Exception Macros 812
  Mixing Exceptions 813

Exceptions—What Are They Good For?

When writing programs, you must be concerned not only with the normal operation of the program but also the kind of events that happen in the real world: unexpected user inputs, low memory conditions, disk drive errors, unavailable network resources, unavailable databases, and so on. One way a programmer can handle such real-world eventualities is to use exceptions.

The concept of handling exceptions is not foreign to software development. The ubiquitous if-then-else construct is often used to handle detection and processing of special cases such as bad user input or the inability to open a disk file. The code snippet in Listing 22.1 shows an example of making such runtime decisions.

Listing 22.1 Example of Using Nested If-Then-Else Statements


void NestedIfExample()
{
   // open an input file, and output file, and a log file
   FILE * fpIn;
   FILE * fpOut;
   FILE * fpLog;
   fpIn = fopen( “c:\\temp\\in.txt”, “r” ); if ( fpIn )
   {
      fpOut = fopen( “c:\\temp\\out.txt”, “w+” );
      if ( fpOut )
      {
         fpLog = fopen( “c:\\temp\\log.out”, “a+” );
         if ( fpOut )
         {
            // do some processing on fpIn producing fpOut
            // while logging to fpLog
            // close all the files
            fclose( fpIn );
            fclose( fpOut );
            fclose( fpLog );
         }
         else
         {
            // error opening log file - close in and out
            fclose( fpIn );
            fclose( fpOut );
            printf( “error opening log file\n”);
         }
      }
      else
      {
         // error opening out file - close the in file
         fclose( fpIn );
         printf( “error opening output file\n”);
      }
   }
   else
   {
      printf(“error opening input file\n”);
   }

}

The code in Listing 22.1 uses nested conditional statements to detect and handle several errors that might occur during the normal processing of a simple function. If the logic becomes complex, the number of possible if-then-else statements, and consequently the level of nesting, becomes excessive and unruly.

Another way programmers have traditionally addressed this issue is to use the goto statement. The goto statement has been accused of many things including the demise of structured programming. Some would tell you the use of the goto is never warranted and that another logic construct must be used.

Sometimes the goto statement can be an elegant solution to the alternative of using nested if-then-else statements. Listing 22.2 shows how the goto statement can be used to simplify the handling of exceptional conditions. As you can see, all the cleanup logic is collected in one place. The same logic is used to deallocate resources regardless of whether the function succeeds or fails. Logic is simplified due to easy detection of which resources have been successfully allocated and hence must be freed up.

Listing 22.2 Example of Using goto Logic


void GotoExample()
{
   // open an input file, and output file, and a log file
   FILE * fpIn = NULL;
   FILE * fpOut = NULL;
   FILE * fpLog = NULL;
   fpIn = fopen( “c:\\temp\\in.txt”, “r” ); if ( ! fpIn )
   {
      printf(“error opening input file\n”);
      goto done;
   }

   fpOut = fopen( “c:\\temp\\out.txt”, “w+” );
   if ( ! fpOut )
   {
      printf( “error opening output file\n”);
      goto done;
   }

   fpLog = fopen( “c:\\temp\\log.out”, “a+” );
   if ( ! fpOut )
   {
      printf( “error opening log file\n”);
      goto done;
   }

   // do some processing on fpIn producing fpOut while
   // logging to fpLog
done:
   // close all the files
   if ( fpIn )
      fclose( fpIn );
   if ( fpOut )
      fclose( fpOut );
   if (fpOut )
      fclose( fpLog );
}

Exceptions are another way to handle the special processing that is required when logic strays from the straight and narrow. Exceptions allow a section of code to be executed under the umbrella of a try block. If things go wrong during the execution of the code, the processor can be vectored to a catch block. The catch block can perform orderly cleanup of resources and program state info as well as logging the cause of the exception such that the source of the problem can be quickly determined and remedied. Listing 22.3 is an example of using exceptions to handle errors. (In this example, resources are cleaned up outside the catch block.)

Listing 22.3 Example of Using Exception Processing


void ExceptionExample()
{
   // open an input file, and output file, and a log file
   FILE * fpIn = NULL;
   FILE * fpOut = NULL;
   FILE * fpLog = NULL;
   try
   {
      fpIn = fopen( “c:\\temp\\in.txt”, “r” );
      if ( ! fpIn ) throw “Error opening c:\\temp\\in.txt”;

      fpOut = fopen( “c:\\temp\\out.txt”, “w+” );
      if ( ! fpOut ) throw “Error opening c:\\temp\\out.txt”;
      fpLog = fopen( “c:\\temp\\log.out”, “a+” );
      if ( ! fpOut ) throw “Error opening c:\\temp\\log.out”;

      // do some processing on fpIn producing fpOut while
      // logging to fpLog

   }
   catch( char * pszCause )
   {
        printf(“%s\n”, pszCause);
   }

   // clean up
   if ( fpIn )
      fclose( fpIn );
   if ( fpOut )
      fclose( fpOut );
   if (fpOut )
      fclose( fpLog );
}

Types of Exceptions

When programming in the MFC/C++ environment, a programmer should be cognizant of two major sources of exceptions: structured exceptions and C++ exceptions generated by MFC. Both of these sources of exceptions come along as “part of the territory” and, although you may choose to close your eyes and ignore them, they will probably occur at some point during the lifetime of your application and hence should at least be considered.

In addition to structured exceptions and MFC-generated exceptions, you can choose to derive your own exception classes based on MFC’s CException class. This can provide an elegant method of passing out-of-the-ordinary conditions up the call stack.



There is also a legacy form of exception handling based on macros included with MFC back in the days before Visual C++ supported standard exceptions. These macros act much the same as the current “blessed” exception-handling logic except for some details regarding the way exception objects are caught and deleted. The differences are examined in detail near the end of this chapter.

As you can see, there are a number of types of exception-handling methodologies to consider. The balance of this chapter starts by examining the features and benefits of structured exception handling. C++ exceptions come next, followed by a simple implementation of the CException class. Then the chapter covers how MFC handles exceptions and gives a brief look at all the standard MFC exception classes. Next, you will focus on creating a custom exception handler class based on CException. Finally, the chapter considers the legacy macros introduced with MFC 1 and discusses how to mix exception-processing methodologies.

Structured Exception Handlers

Win32 implements structured exception handlers as a means of enabling applications to detect low-level events such as divide-by-zero errors, memory access violations, exceeding array bounds, floating-point overflow and underflow conditions, and so on. These low-level errors are often caused by faulty application logic, but nonetheless will result in an application terminating in a blaze of flames. Without an exception handler, the user will see a dialog box indicating that the program generated an unhandled exception and the application will be terminated. Figure 22.1 shows a typical dialog box.


Figure 22.1  Divide-by-zero structured exception dialog.

When the application dies, it does so in a very uncooperative fashion. The application does not get an opportunity to save state, close open files and databases, serialize objects, release GDI resources, and so on. The results can lead to instabilities in the application the next time the application runs.

This can be remedied by providing a _try block and an associated _except block. (Don’t confuse the _try/_except syntax used by structured exception handlers with the try and catch keywords used by C++ exceptions. As you’ll see later, the two are completely different beasts.)

So how do you implement the _try/_except block? Let’s take a look at a simple example and see how it works.

Listing 22.4 implements a simple exception handler to catch divide-by-zero errors.

Listing 22.4 A Simple Structured Exception Example


try
{
   int i, zero;
   zero = 0;
   i = 1/zero;
   i = 1;
}
_except( EXCEPTION_EXECUTE_HANDLER )
{
   ::MessageBox( NULL, “Saw exception...”, “”, MB_OK );
}

int j = 100;

The code starts with a _try block that has an obvious and intentional error that generates a divide-by-zero error. When the exception occurs, the processor is vectored to the _except block. The EXCEPTION_EXECUTE_HANDLER keyword causes code within the _except block to be executed. In this case, a simple MessageBox() is raised and the program continues without a hitch. Note that the line I = 1; in the _try block is never reached. After the _except block executes, control returns to the first statement that follows the _except block, in this case j = 100.

Several things are going on in this simple example. The code in the _try block is very simple in this case but in real life could include a lot of intricate logic. In fact, the _try block could wrap the entire logic of your application. The _try block would then protect the entire application against divide-by-zero errors.

The _except block is optionally executed based on the outcome of what’s called a filter. The filter is the expression that is contained within the parentheses of the _except statement. In the case of the sample code, it is the constant EXCEPTION_EXECUTE_HANDLER. The filter associated with an _except block should evaluate to one of three values:

  EXCEPTION_CONTINUE_EXECUTION (-1)
  EXCEPTION_CONTINUE_SEARCH (0)
  EXCEPTION_EXECUTE_HANDLER (1)

In the previous example, the exception handler would be executed for all types of structured exceptions. In practice, the filter would often conditionalize execution based on the results of GetExceptionCode() as in the following example:

except(GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO)

If the filter returns 0, the search for another _except block continues. A single _try block can be followed by multiple _except blocks, each with a different filter. The _except blocks are searched until a filter is found that requests the continuation of execution by evaluating to -1 or that requests the execution of the “handler” by evaluating to 1. What’s more, nested _try blocks with associated _except blocks can be spread out over one or more functions or methods. The search for a handler will continue to look up the call stack until it finds a handler that directs otherwise. If the search for a handler is not fruitful, the default handler takes over and terminates the application in a way that is similar to the behavior you saw when there were no _try blocks or _except handlers.

If the filter evaluates to 1, the code within the block executes (as in this simple example). The rest of the code in the _try block is short-circuited, and execution continues at the statement after the _except block(s) contained in the method that handles the exception.

If a filter resolves to EXCEPTION_CONTINUE_EXECUTION (-1), execution returns to the same statement that caused the exception in the first place. Unless some external event occurs to resolve the reason for the exception, an endless loop will result.

Nesting of Structured Exception Handlers

One big advantage of structured exceptions is the ability to unwind a frame stack when an exception is raised. In traditional code, a series of nested functions (or methods) would have to unwind by each function returning in order. If each function passes on its subordinate’s return code, eventually the error is bubbled to the top of the stack. If one of the subordinate functions fails to pass along the return code, the cause of the error might easily be lost.

With exception handlers, an error condition can be detected deep down in the call stack and can be bubbled to the top of the stack without any functions or methods returning. If the _except block that handles the exception is at the top of the stack, all call frames are automatically “popped” off the stack. All automatic (stack-based) variables drop out of scope. (Static variables remain accessible after an exception is handled.) This behavior can be similar to using the exit() statement in a controlled fashion or can be localized to a single function or set of functions.



Listing 22.5 demonstrates using exception handling in a nested function call environment.

Listing 22.5 Structured Exceptions in a Nested Environment


int CTestSEHExceptions::TestNestedSEH( void )
{
   _try
   {
      SEHCaller();
   }
   _except(GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO)
   {
      ::MessageBox(NULL,“Saw nested SEH exception...”,“”,MB_OK );
   }

   // execution continues here after the exception handler runs
   return 0;
}
int CTestSEHExceptions::SEHCaller( void )
{
   return SEHGenerator();
}
int CTestSEHExceptions::SEHGenerator( void )
{
   int i, zero;
   zero = 0;
   i = 1/zero;

   return i;
}

In this simple and somewhat contrived sample class, CTestSEHExceptions contains a member method called TestNestedSEH() that contains a simple _try and _catch block. Within the _try block a call is made to method SEHCaller(). SEHCaller() subsequently calls SEHGenerator(), which performs a divide-by-zero. When the exception occurs, the CPU is executing two call frames down the stack. The first _except block found will handle the exception. The code within that _except block is immediately invoked. After the exception is handled, processing continues in TestNestedSEH(), as the comment indicates.

Raising Structured Exceptions

Raising structured exceptions is not something an application developer typically would do. Several structures associated with structured exceptions contain low-level information regarding the cause of the exception. Without access to hardware-level registers, these structures cannot be filled in. Generating structured exceptions is typically the responsibility of device drivers or other kernel mode logic.

The CONTEXT structure associated with a structured exception contains processor-specific information such as register values and stack pointers. The information is required in order that processing can be continued from the exact point where the infraction was detected if the exception is successfully handled. The EXCEPTION_CONTEXT structure is also associated with a structured exception. It contains a machine-independent description of the exception. Applications can receive pointers to these two structures from within an exception handler by calling the Win32 ::GetExceptionInformation() function.

The best way to raise an exception (for testing purposes, for example) is to simply perform an operation that causes the kernel mode code within the operating system to do the dirty work for you. In the examples in this chapter, an integer divide-by-zero has been used. Other structured exceptions can be raised easily enough. Getting creative with the _asm statement can render all sorts of structured exceptions!

Cleaning Up After an Exception

The ability of structured exception handling to unwind the stack is a powerful feature that sets it apart from other techniques of handling errors such as nested if statements, goto statements, or calling an error routine. But the automatic unwinding of the stack can cause a complication. What happens to resources allocated by the methods and functions that are removed from the call stack when an exception occurs? If the resources are not released, many problems can crop up, including memory leaks, resource starvation, and deadlocks due to resources being owned forever.

Structured exception handling provides the _try/_finally keywords as a means to deal with these issues. The way _try/_finally blocks work is as follows:

1.  The code in the _try block is executed.
2.  Whenever the processor leaves the _try block for any reason, the code in the _finally block is executed.

The “for any reason” in item 2 is quite literal. Even if the _try block is exited through a goto statement or an exception being thrown, the code in the _finally block is executed.

A great way to handle resources allocated within a function or method is to include the cleanup code within a _finally block. This assures that you will get a chance to free up memory, file handles, semaphores, mutexes, and so on when an exception is thrown.

Listing 22.6 demonstrates using the _try/_finally mechanism of structured exception handling.

Listing 22.6 Structured Exception _finally Example


int CTestSEHExceptions::TestSEHTermHandler( void )
{
   int retval = 0;
   try
   {
      retval = SEHTermA();
   }
  _except(GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO)
   {
      ::MessageBox(NULL,“Saw SEH exception...”,“”,MB_OK );
   }

   return 0;
}

int CTestSEHExceptions::SEHTermA( void )
{
   _try
   {
      return SEHTermB() + 1;
   }
   _finally
   {
      ::MessageBox(NULL,“At Termination Handler A...”,“”,MB_OK );
   }

   return 0;
}

int CTestSEHExceptions::SEHTermB( void )
{
   _try
   {
      return SEHTermC() + 1;
   }
   _finally
   {
      ::MessageBox(NULL,“At Termination Handler B...”,“”,MB_OK );
   }

   return 0;
}

int CTestSEHExceptions::SEHTermC( void )
{
   _try
   {
      return SEHGenerator() + 1;
   }
   _finally
   {
      ::MessageBox(NULL,“At Termination Handler C...”,“”,MB_OK );
   }

   return 0;
}
int CTestSEHExceptions::SEHGenerator( void )
{
   int i, zero;
   zero = 0;
   i = 1/zero;

   return i;
}

The TestSEHTermHandler() calls SEHTermA(), which calls SEHTermB(), which calls SEHTermC(). SEHTermC calls SEHGenerator(), which causes a divide-by-zero exception. The _except handler that catches the divide-by-zero exception is all the way at the top of the call stack. Normally SEHTermA, SEHTermB, and SEHTermC would be immediately exited without regard for any cleanup that might be required. Because _finally blocks were included, they are invoked beginning with SEHTermC() and ending with SEHTermA(). In a real-world application, the MessageBox would be replaced by code that released any resources owned by the method.

C++ Exceptions

C++ provides a rich exception-handling environment that differs a bit from the structured exception-handling environment you have just looked at. First, C++ exception handlers are intended to be invoked by logic created by the programmer and for the programmer. Whereas structured exception handlers are invoked when low-level hardware events take place, C++ exceptions occur when the programmer places a throw statement in the path of the processor. Where structured exception handling is more a rigid mechanism for the OS to notify your application of external events, C++ exceptions are a tool made available to the programmer in order to better handle anomalies as they arise in application logic.

The general BNF syntax for C++ exceptions is as follows:

try-block :
    try compound-statement handler-list
handler-list :
   handler handler-listopt
handler :
    catch ( exception-declaration ) compound-statement
exception-declaration :
   type-specifier-list declarator

type-specifier-list abstract-declarator

   type-specifier-list
...

throw-expression :
   throw assignment-expressionopt



So what does all that mean? Well, a sample is worth a thousand words. Listing 22.7 shows how simple it is to incorporate exception handling into a C++ application.

Listing 22.7 A Simple C++ Exception Example


int main(int argc, char* argv[])
{
   try
   {
      // lots of logic ultimately causes the world to degrade
      // to the point that your code gives up...
      throw “I’m melting...”;
      throw 1.234; //never executed but would work
      throw 9; // also never executed but also valid
   }
   catch( int i)
   {
      printf(“Saw the int exception %d\n”, i);
   }
   catch( char * szCause )
   {
      printf(“Saw the string exception - %s\n”, szCause );
   }
   catch(...)    //ellipsis handler must be last
   {
      printf(“Saw some other kind of exception”);
   }

   return 0;
}

After looking at the structured exception-handling examples presented previously in this chapter, this should all seem pretty clear. Once again you wrap a section of code within a try block. This block of code is also known as a “guarded” block. At some point during the execution of the guarded block, an exception might (or in this example will absolutely) be raised. One or more handlers denoted by the catch keyword follow the try block. The type of the parameter passed during the throw operation determines which handler is entered. A “catch-all” (excuse the pun), default handler identified by an ellipsis may be included provided it’s the last handler in the list. The handlers are “searched” in the order they appear after the try block. The search can continue up the call stack into any try/catch blocks that may appear in the current execution context.

After the exception is handled, execution continues with the statement following the last catch block of the try block that finally caught the exception. If the try block that caught the error is not the one who raised the exception, but rather one implemented higher up the call stack, the exception will cause the stack to be popped of all call frames subordinate to the function (or method) that caught the exception. That is, multiple functions or methods can be terminated simultaneously by throwing an exception handled somewhere up the call stack.

What if no exception is thrown? Execution continues until all statements within the try block are executed. Execution then jumps over all the catch blocks and continues from there. (In the sample code shown previously, execution would continue at the return 0; statement.)

What if no handler is found that is willing to process the exception? C++ specifies that a function called terminate() will be invoked. The default terminate() function raises an application modal dialog that informs the user that an unhandled exception has occurred and then terminates the application. Figure 22.2 shows what the terminate() dialog looks like.


Figure 22.2  Unhandled C++ exception dialog box.

The code within a catch block can only be entered through a thrown exception. It is illegal to try to enter a handler any other way. Handlers and exceptions are clearly a parallel code path that the programmer can take as logic dictates.

When an exception causes the stack to unwind, automatic variables are deleted. (Automatic variables are those allocated on the stack.) So if you create an automatic instance of a class, as Listing 22.8 does, the destructor of the class will be invoked when the stack unwinds.

Listing 22.8 Throwing Exceptions with Automatic Variables


int myFunc( void )
{
   CSimple Simple1;
   throw “Cool Stuff”;
   return 0; // never reached
}

If, on the other hand, you create the instance of a class through new (or malloc some memory, open a file handle, and so on), the destructor will not be called and a resource leak will occur. Listing 22.9 exemplifies this potential problem.

Listing 22.9 Throwing Exceptions with Heap Variables


int myFunc( void )
{
   CSimple * pSimple1 = new CSimple; // will not be deleted
   throw “Not So Cool Stuff”;
   return 0; // never reached
}

You must take care to allocate resources in a way that coexists with your use of exception handlers. One approach is to allocate and deallocate all resources outside the scope of a try block. The try block and related exceptions are then used simply for logic or runtime errors that might occur during the course of execution. Another approach is to allocate resources within a try block, but use pointers and handles that are declared at sufficient scope that the exception handler can test for non-null pointers and handles and free up the resources in the event of an exception.

Defining a C++ Exception Class

As you have seen, C++ exceptions provide the mechanism to catch and handle an exception based on the type of the variable referenced in the throw statement. The previous examples dealt only with intrinsic C++ variable types: integers, singles, doubles, characters, character pointers, and so on. But C++ enables you to create additional types of variables through the use of structures, unions, and classes. What if you create a class called CException that supported saving the cause of the exception in a string that could be displayed to the user at an appropriate point in time? Could you throw the CException class and have it be detected and handled in similar fashion to integers and strings? The answer is yes. Listing 22.10 provides a simple example.

Listing 22.10 A Simple CException Implementation


#include “stdafx.h”
#include “string.h”

class CException
{
private:
   int   m_iError;
   char  m_szError[255];

public:
   CException() { m_iError = 0; m_szError[0] = ‘\0’; }
   int GetErrno( void ) { return m_iError; }
   void SetErrno( int error ) { m_iError = error; }
   int GetErrorMessage( char * lpszError, unsigned uMaxError )
   {
      if ( lpszError && (uMaxError > strlen( m_szError )) )
      {
         strncpy( lpszError, m_szError, uMaxError );
         return 1;
      }
      return 0;
   }

   void SetErrorMessage( char * lpszError )
   {
      strncpy(m_szError, lpszError, 255);
      m_szError[ 255 ] = ‘\0’;
   }
};

int SomeFunc( void )
{
   CException e;
   e.SetErrno( 99 );
   e.SetErrorMessage( “Throwing CExceptions” );
   throw e;
   return 0; // never reached
}

int main(int argc, char* argv[])
{
   try
   {
      SomeFunc();
   }
   catch( CException e )
   {
      char szerr[ 255 ] = “/0”;
      e.GetErrorMessage( szerr, 255 );
      printf(“Saw CException %d - %s\n”, e.GetErrno(), szerr );
   }

   return 0;
}



MFC Exceptions

Given the intensely hot exception-handling capabilities of C++ and the coolness of MFC in general, the developers at Microsoft figured they had to combine the two and came up with MFC exceptions.

MFC defines its own CException class that is derived from CObject. As such, all MFC exception classes inherit the base functions of CObject. These include Dump(), AssertValid(), IsSerializable(), Serialize(), GetRuntimeClass(), and IsKindOf().

The IsKindOf() method is very useful when dealing with exceptions. Using IsKindOf() allows a generic CException handler to ascertain what kind of exception it is dealing with. If you plan on deriving any of your own exception classes based on CException, you should make certain they contain runtime class information required by IsKindOf(). You can accomplish this by using the DECLARE_DYNAMIC and IMPLEMENT_DYNAMIC macros defined by MFC. (You’ll see an example of using these macros a bit later in the chapter.)

The CException class adds a few additional functions to the CObject implementation. The added methods provide a base level of functionality that all CException-derived classes will want to utilize. These methods include the CException constructor, Delete(), GetErrorMessage(), and ReportError().

The CException constructor has the following signature:

CException( BOOL b_AutoDelete );

The default value for b_AutoDelete is TRUE. (There are actually two constructors. One takes zero parameters and sets m_bAutoDelete to TRUE. The second constructor requires a BOOL parameter that is used to set m_bAutoDelete to either TRUE or FALSE.)

The member method Delete() works in conjunction with the CException::m_bAutoDelete flag to better handle the differences between stack-based and heap-based exceptions. The implementation of Delete() is pretty simple. It checks to see if the m_bAutoDelete flag is set and if so it calls “delete this.”

The reason MFC provides the m_bAutoDelete flag and the Delete() method is to make it easy for exception handlers to delete the exception object when they are finished with it. When exception handlers are passed an exception object, the handler doesn’t know if the exception object was allocated on the stack, created on the heap through the new operator, or created as a global or static object. The exception handler is responsible for deleting the exception object when it is done with it. (After all, the code that created the exception object will not be returned to after the exception is thrown. Someone’s got to delete the exception object!) The combination of properly setting the m_bAutoDelete flag, in conjunction with ensuring that the exception handler calls Delete() when it’s through processing the exception, helps ensure that no memory leaks are caused by the exception process.

The GetErrorMessage() method of CException is intended to be overridden by your CException-derived class. The base class implementation does little other than set the return values to 0. The signature is slightly different from the GetErrorMessage() implemented in the example shown earlier. CException::GetErrorMessage() looks like this:

BOOL GetErrorMessage( LPTSTR lpszError,
                      UINT uMaxError, PUINT pnHelpContext = NULL );

The lpszError and uMaxError parameters work pretty much like the sample implementation. The lpszError parameter points to a buffer where the error message should be stored. The uMaxError parameter specifies the maximum number of bytes that can be returned, which probably corresponds with the size of the buffer pointed to by lpszError. The additional, and optional, parameter is a pointer to an unsigned int that is used to return a help context resource id. As previously mentioned, the base class returns a zero-length error string, a 0 for a help context id, and FALSE as its completion status. Derived classes will need to implement a specialization of this method providing valid error strings and help ids.

The ReportError() method completes the base class. The signature for this method looks like this:

CException::ReportError( UINT nType = MB_OK, UINT nError = 0 );

ReportError raises an application modal dialog box based on the string and help context id returned by GetErrorMessage(). If GetErrorMessage() returns FALSE (probably because the derived class didn’t implement it), the nError parameter is used as a string table resource ID that will be used to display a modal dialog box.

That completes the CException base class. Let’s look at how MFC extends the functionality of the base class by deriving several specializations.

MFC CException-Derived Classes

MFC uses exceptions to inform application logic when things go awry. The following sections provide an overview of the exception classes provided with MFC.

CMemoryException

CMemoryException is used by MFC to inform applications that an out-of-memory condition has occurred. No additional public member variables or methods are available.

Microsoft’s implementation of the new handler throws the CMemoryException. If you choose to replace new() with your own rendition, you must remember to throw CMemoryExceptions too. Other parts of MFC count on CMemoryException to trap low-memory conditions. If you don’t throw the exception, other parts of MFC will behave in an undefined fashion.

It is recommended that you use AfxThrowMemoryException() rather than creating a instance of CMemoryException and throwing it yourself.

CNotSupportedException

The CNotSupportedException is raised by MFC when an application requests an unsupported feature. There are no public members or methods beyond those provided by CException to qualify the exception.

There are about 30 instances in MFC that throw the CNotSupportedException. They can be found in the CArchive, CMemFile, CDataExchange, CStdioFile, CInternetFile, CGopherFile, and CDataExchange classes, among others.

If you want to raise a CNotSupportedException, you should use the AfxThrowNotSupportedException() function.



CArchiveException

The CArchiveException is raised by CArchive member functions if something goes wrong while serializing a class instance. CArchiveException has one public member that can be used to determine the cause of the exception. Surprisingly, it is called m_cause. There is an enumeration associated with m_cause that provides a portable (OS-independent) error code. Table 22.1 lists the possible values CArchiveExeception’s m_cause can take on.

Table 22.1 CarchiveException::m_cause Values

Constant Description

CarchiveException::none Encountered no errors
CArchiveException::generic Encountered an unspecified error
CArchiveException::readOnly Attempted to write to a read-only archive
CArchiveException::endOfFile Encountered end of file during a read
CArchiveException::writeOnly Attempted to read from a write-only archive
CArchiveException::badIndex Encountered an invalid file format
CArchiveException::badClass Attempted to read an object into an object of the wrong type
CArchiveException::badSchema Attempted to read an object with a different version of the class

Note that CFileException also has an m_cause member variable that takes on a similar set of values. The two enumerations are distinct.

The AfxThrowArchiveException() function can be used to raise a CArchiveException if you ever need to. Using the AFX function to raise the exception is the recommended procedure.

CFileException

CFileException is used to inform applications of file system-related errors. CFileException extends the base functionality of CException by including several public data members and methods that help determine which file has run into a problem and hence caused the exception to be raised.

The public member m_strFileName contains the name of the file that is associated with the exception. This is valuable because without it the application would have to keep track of which file was being accessed when the exception occurs. Making the association at the point of failure is a much better solution.

The m_cause public member provides a “portable” (non-OS-specific) error code. It will take on a value based on an enumeration contained in CFileException. Table 22.2 provides a list of possible values.

Table 22.2 CfileException::m_cause Values

Constant Description

CFileException::none Encountered no errors
CFileException::generic Encountered an unspecified error
CFileException::fileNotFound Could not locate the file
CFileException::badPath Encountered an invalid path
CFileException::tooManyOpenFiles Exceeded the maximum number of open files
CFileException::accessDenied Could not access the file
CFileException::invalidFile Attempted to use an invalid file handle
CFileException::removeCurrentDir Could not remove the current working directory
CFileException::directoryFull Could not create any additional directory entries
CFileException::badSeek Could not set the file pointer
CFileException::hardIO Encountered a hardware error
CFileException::sharingViolation Could not load SHARE.EXE, or a shared region was locked
CFileException::lockViolation Attempted to lock a region that was already locked
CFileException::diskFull Attempted to write to full disk
CFileException::endOfFile Reached end of file

The m_IosEerror public data member is a LONG that contains the Input/Output System-specific error code associated with the exception. This member contains values from either error.h or errno.h. If MFC was ported to a new operating environment that had a pre-existing set of error codes for the disk subsystem, they would be reflected in this member variable.

The idea is that the exception carries along both a portable and an IOS-specific code. The application can use whichever it pleases depending on the level of detail required.

AfxThrowFileException() is the recommended method of creating and throwing a CFileException. AFXThrowFileException() takes the portable cause, the IosError, and the filename as inputs, creates a CException object on the heap using the new operator, and then throws the exception.

The CFileException constructor takes three parameters: the portable “cause,” the IosError, and the lpszArchiveName. All will default to 0 if not specified.


Note:  

The documentation on the MSDN CD-ROM indicates that only two parameters are accepted by the constructor: the cause and the IosError. One nice thing about MFC is that you get the source. When in doubt, use the source (Luke)!




Two public conversion methods, OsErrorToException() and ErrnoToException(), are used by CFileException when implementing two other public methods: ThrowOsError() and ThrowErrno(). The conversion functions are used to convert the IOS-specific errors supplied as parameters to ThrowOsError() and ThrowErrno() into a portable format required by the CFileException constructor. (The gory details can be found in ..\MFC\SRC\Filex.cpp.)

The OSErrorToException( LONG IosError ) method is used to convert an IOS error (as found in error.h) into a portable error equivalent. The IOS error space might be more extensive than the portable error space. Hence the method might return CFileException::generic if there is no exact mapping. ErrnoToException( int Errno ) performs the similar conversion for DOS errors as found in errno.h.

ThrowOsError() takes the IOS error code (from error.h) and the filename and, after converting the IOS error code to a portable “cause,” invokes AfxThrowFileException() in order to get the dirty work done.

ThrowErrno() takes the DOS errno (from errno.h) and the filename and, after converting the errno to a portable “cause,” invokes AfxThrowFileException() as well.

In both cases the m_IosError member of CFileException() is set to the OS-specific error code. Maybe it comes from error.h. Maybe it comes from errno.h. The application will need to know what it’s working with if it uses the m_IosError public member. You’re probably better off using the portable version if at all possible.

CResourceException

MFC raises the CResourceException when Windows cannot find or allocate a requested resource. No additional public are members available by which to qualify the exception.

There are about 30 incidences in MFC where the CResourceException is raised. Examples of activities that cause MFC to raise this exception include creating brushes, bitmaps, mutexes, events, loading strings, accessing sockets, and so on.

The AfxThrowResourceException() function is the recommended way to raise a CResourceException.

COleException

MFC raises this exception whenever an OLE error occurs. A public member called m_sc contains the error code that caused the exception. The m_sc data member is of type SCODE, which is an OLE status code. OLE status codes contain bit-mapped fields for severity, context, facility, and code.

There are two versions of AfxThrowOleException(). One expects an SCODE as a parameter, whereas the other expects an HRESULT. If an HRESULT is passed, AfxThrowOleException() converts the HRESULT to an SCODE and then creates an instance of COleException on the heap prior to throwing the exception.

COleException has one static public method called Process(). You can pass COleException::Process() a pointer to any CException-derived object and in return you get an OLE SCODE back that describes the error associated with the passed exception. So if you pass a CMemoryException, you get back an E_OUTOFMEMORY SCODE. Or if you pass in a CNotSupportedException, you get back an E_NOTIMPL SCODE.

CDbException

MFC uses the CDbException to alert applications about errors raised by ODBC database accesses. (A separate exception class is available to return errors associated with DAO database accesses.)

CDbException has three public member variables that help clarify the cause of the exception. The m_nRetCode variable is of type RETCODE and essentially represents the error value returned by the ODBC API. Table 22.3 provides a list of the possible return codes.

Table 22.3 CDbException::m_RetCode Values

Constant Description

AFX_SQL_ERROR_API_CONFORMANCE The driver used when calling CDatabase::OpenEx or Cdatabase::Open does not conform to required ODBC level.
AFX_SQL_ERROR_CONNECT_FAIL The connection to the data source failed.
AFX_SQL_ERROR_DATA_TRUNCATED Insufficient storage was provided for the requested data.
AFX_SQL_ERROR_DYNASET_NOT_SUPPORTED The driver does not support dynasets.
AFX_SQL_ERROR_EMPTY_COLUMN_LIST No columns were identified in the record field exchange (RFX) function calls in your DoFieldExchange override.
AFX_SQL_ERROR_FIELD_SCHEMA_MISMATCH The RFX function(s) specified in the DoFieldExchange override is not compatible with the column data types of the recordset.
AFX_SQL_ERROR_ILLEGAL_MODE CRecordset::Update was called without previously calling CRecordset::AddNew or CRecordset::Edit.
AFX_SQL_ERROR_LOCK_MODE_NOT_SUPPORTED The ODBC driver does not support locking.
AFX_SQL_ERROR_MULTIPLE_ROWS_AFFECTED A call to CRecordset::Update or Delete was made for a table with no unique key, and changes were made to multiple records.
AFX_SQL_ERROR_NO_CURRENT_RECORD An attempt was made to edit or delete a previously deleted record.
AFX_SQL_ERROR_NO_POSITIONED_UPDATES A request for a dynaset failed because the ODBC driver does not support positioned updates.
AFX_SQL_ERROR_NO_ROWS_AFFECTED A call to CRecordset::Update or Delete was made, but the record could no longer be found.
AFX_SQL_ERROR_ODBC_LOAD_FAILED An attempt to load the ODBC.DLL failed; Windows could not find or could not load this DLL. This error is fatal.
AFX_SQL_ERROR_ODBC_V2_REQUIRED A request for a dynaset failed because a Level 2-compliant ODBC driver was required.
AFX_SQL_ERROR_RECORDSET_FORWARD_ONLY An attempt to scroll did not succeed because the data source did not support backward scrolling.
AFX_SQL_ERROR_SNAPSHOT_NOT_SUPPORTED A call to CRecordset::Open failed because it isn’t supported by the driver.
AFX_SQL_ERROR_SQL_CONFORMANCE CDatabase::OpenEx or CDatabase::Open failed because the driver does not conform to the required ODBC SQL Conformance level of Minimum (SQL_OSC_MINIMUM).
AFX_SQL_ERROR_SQL_NO_TOTAL The ODBC driver was unable to specify the total size of a CLongBinary data value.
AFX_SQL_ERROR_RECORDSET_READONLY An attempt to update a read-only recordset failed, or the data source is read-only.
SQL_ERROR A function failed because the error message returned by ::SQLError is stored in the m_strError data member.
SQL_INVALID_HANDLE A function failed due to an invalid environment handle, connection handle, or statement handle.



CDbException also includes a public member that describes the error through a string. m_strError contains a zero-terminated string of TCHARs that is initialized at the same time as m_nRetCode.

One additional public member is provided in order to help the application determine the cause of the error. This is the m_strStateNativeOrigin string. It is built of three components, as the name implies. The information is basically a repackaged version of the information returned by ::SQLError(). The format of the string is “State:%s,Native:%ld,Origin:%s”. An example of an m_strStateNativeOrigin string would be “State:S0022,Native:207,Origin:[Microsoft][ODBC SQL Server Driver][SQL Server]”.

An AfxThrowDbException() function is available for those who would like to generate CDbExceptions.

COleDispatchException

MFC provides the COleDispatchException exception for use with errors associated with the OLE IDispatch interface. IDispatch is a key part of implementing OLE automation. This exception is very closely tied to the application that is being automated, namely the one you’re writing. The error codes, error strings, help context ids, help files, and so on returned by the public members of COleDispathException are all related to the application being automated.

Several public methods can be used to qualify the exception. m_wCode is an application-specific error code describing the cause of the exception. The m_strDescription provides a textual representation of m_wCode. The m_dwHelpContext is a DWORD that identifies a help context associated with the exception. The m_strHelpFile identifies the help file for which m_dwHelpContext refers. The m_strSource identifies the application that generated the exception.

Two versions of AfxThrowOleDispatchException() are available to aid in throwing COleDispatchExceptions. Both accept parameters for specifying the m_wCode and w_dwHelpContext values. The difference between the two versions is that one accepts lpszDescription, whereas the other accepts nDescriptionID, which is used to reference a string table from which the error description is pulled.

AfxGetApp()->m_pszHelpFilePath is used to fill in m_strHelpFile. AfxGetAppName() is used to fill in the m_strSource member variable.

CUserException

The CUserException is provided by MFC as a means of handling abnormal events that might occur while executing some user-requested process. Let’s say your application performs a statistical analysis of stock market data. Somewhere during the processing, say a dozen method calls down in the bowels of the logic, a critical error occurs. Rather than unwinding the stack by returning from each method call (which means making sure each method knows how to handle error responses returned by called methods), the application programmer might choose to throw a CUserException.

Use AfxThrowUserException() to raise CUserExceptions. AfxThrowUserException() takes no parameters.

CDaoException

MFC database classes based on DAO (Data Access Objects) raise the CDaoException when things go awry. Several public members and methods are available to help determine the cause of the exception.

The m_scode public member contains an OLE SCODE that describes the error. You probably won’t use the member variable too often because numerous other variables are available to aid in diagnosing the exception.



The m_nAfxDaoError is a more useful source of information. It contains the extended DAO error code associated with the exception. For more information about DAO extended error codes, refer to the Chapter 18, “MFC Database Processing.” Table 22.4 shows the list of possible values.

Table 22.4 CdaoException::m_nAfxDaoError Values

Constant Description

NO_AFX_DAO_ERROR The most recent operation could have produced an error from DAO or OLE. Check m_pErrorInfo and possibly m_scode for more information.
AFX_DAO_ERROR_ENGINE_INITIALIZATION An error occurred while initializing the Microsoft Jet database engine.
AFX_DAO_ERROR_DFX_BIND An address used in a DAO record field exchange (DFX) function does not exist or is invalid.
AFX_DAO_ERROR_OBJECT_NOT_OPEN An attempt to open a recordset based on a querydef or a tabledef object failed because the object was not in an open state.

The m_pErrorInfo public data member contains a pointer to a CDaoErrorInfo object. This object allows access to an error code, a textual description of the error, the name of the application or data source that generated the error, a path to a help file, and a help context id. This is probably the most valuable member of the CDaoException class.

When a CDaoException is raised, m_pErrorInfo is initialized with information regarding the most recent error. But you can extend the benefit of m_pErrorInfo by using two other methods made available through CdaoException: GetErrorCount() and GetErrorInfo().

GetErrorCount() returns a short that indicates the number of error objects in the database engine’s Errors collection. You can loop through the available errors to gain more insight into the specific cause of an exception. The value returned by GetErrorCount() determines the range of valid index values you can pass to GetErrorInfo(). Calling GetErrorInfo() with an index within the range of 0 .. ( GetErrorCount() - 1 ) will load m_pErrorInfo with new information relating to the requested error instance.

CDaoExceptions are usually raised by the MFC framework. You probably won’t find much reason to raise your own exceptions. An AfxThrowDaoException() function is available in the unlikely event you want to throw a CDaoException. AfxThrowDaoException() takes no parameters. Rather it relies on the DAO database engine to provide the information required to fill in the exception.

CInternetException

MFC contains a number of classes directly targeted at making Internet access easier. These classes include CInternetSession, CInternetFile, CFtpConnection, CGopherConnection, CHttpConnection, and CHttpFile, all of which throw CInternetExceptions when things go awry.

The CInternetException contains two public data members that can shed light on the cause of the exception. The m_dwError member contains an error code based on the system errors delineated in WINERROR.H or the Internet class-specific error values found in WININET.H.

The second member element of CInternetException is m_dwContext. The value contained in m_dwContext is the same value assigned to a CInternetSession through the constructor. The dwContext is important when using asynchronous Internet operations because it identifies the CInternetSession when callbacks occur as the processing of Internet requests proceeds.

An AfxThrowInternetException() function is available if you want to throw your own exception. AfxThrowInternetException() takes two parameters: the error code and the context identifier.

Deriving Your Own MFC-Compliant Exception Objects

As you have seen, MFC provides a CException base class from which sevel MFC-supplied derivations exist. You can use the member methods and data elements of the MFC derivations when you catch exceptions thrown by the framework. You can also throw your own exceptions using the AfxThrow... functions provided for all the MFC-supplied exception classes.

Taking this one step further, you can derive your own specializations based on CException. You can then throw and catch instances of these exceptions as appropriate and beneficial to your application. You can include any public data members or methods that make sense within the context of exception handling.

Deriving CException-based classes is similar to deriving any other C++ class except that you need to include the DECLARE_DYNAMIC macro in the header file and the IMPLEMENT_DYNAMIC macro in the implementation (cpp) file. These macros work together with MFC to provide runtime type information for the derived exception class. That is, this enables use of the IsKindOf() method, which is quite valuable if you want to write exception handlers that conditionalize their logic based on the type of CException they are dealing with.

Let’s start by looking at the header file (see Listing 22.11) for a new exception class, CAppException.

Listing 22.11 CAppException Header File


class CAppException : public CException
{
   DECLARE_DYNAMIC( CAppException );
public:
   unsigned long m_ulTheCause;
public:
   CAppException() { m_ulTheCause = 0; }
   CAppException( unsigned long cause ) { m_ulTheCause = cause; }
   BOOL GetErrorMessage( LPTSTR lpszError,
                         UINT nMaxError,
                         PUINT pnHelpContext = NULL );
};

Note that the class is derived from CException. I’ve included the DECLARE_DYNAMIC macro as the first line in the class definition. The DECLARE_DYNAMIC macro can be placed anywhere in the header file, but putting it in the class definition itself is totally appropriate. CappException includes one public data member called m_ulTheCause. It can be initialized through the constructor. The class also implements its own version of GetErrorMessage(). This is important because the ReportError() implementation from the base class utilizes GetErrorMessage() when building a dialog box used to inform the user of the error.

Next let’s look at the implementation (cpp) file shown in Listing 22.12.

Listing 22.12 CAppException Implementation


#include “stdafx.h”
#include “TestExceptions.h”

IMPLEMENT_DYNAMIC( CAppException, CException )

BOOL CAppException::GetErrorMessage
(
   LPTSTR lpszError,
   UINT nMaxError,
   PUINT pnHelpContext
)
{
   TCHAR szErr[ 255 ];
   stprintf( szErr, “Fatal Error - %ld”, m_ulTheCause );

   // check if the buffer is too small
   if ( nMaxError <= _tcslen( szErr ) )
      return FALSE;

   tcsncpy( lpszError, szErr, nMaxError );
   if (pnHelpContext ) *pnHelpContext = 0;
   return TRUE;
}



Pretty simple stuff. The IMPLEMENT_DYNAMIC macro is included after the header files. It tells MFC that CAppException is derived from CException. This is the second step required to enable runtime type checking within MFC.

Next, there is a simple implementation of GetErrorMessage(). The m_ulTheCause data member is converted into a string. No help contexts are associated with this simple example, so the value of zero is returned through the supplied pointer.

Now let’s look at a simple usage of CappException in Listing 22.13.

Listing 22.13 Using CAppException


int CTestMFCExceptions::TestAppException( void )
{
   unsigned short szErrString[ 255 ];
   int i = -1;
   try
   {
      CAppException * pe = new CAppException( 99 );
      throw( pe );
   }
   catch( CAppException * e )
   {
      e->GetErrorMessage( (LPTSTR)&szErrString, 255 );
      e->ReportError();
      e->Delete();
      ::MessageBox(NULL,“Saw custom App exception...”,“”,MB_OK );
   }

   return 0;
}

The try block does nothing more than create an instance of CAppException on the heap through the new operator. The constructor initializes m_ulTheCause to 99. The exception is then thrown.

The catch block (handler) looks exclusively for CAppException pointers. The handler calls GetErrorMessage() just to prove it works. (Use the debugger to verify it for yourself.) ReportError() is then called to raise a dialog box identifying the error. Finally, the Delete() method of the base class is called to ensure the exception object is removed from the heap. If you fail to call Delete(), a memory leak will be detected and reported by the debugger.

Deleting Exceptions

As pointed out several places within this chapter, memory and resource leaks can arise from using exceptions. One cause is the exception object itself. MFC typically creates an exception on the heap through the new operator. It then throws a pointer to the exception. After the exception is handled, the processor never returns to the logic that threw the exception. But the exception must be removed from the heap somehow. The convention is for the handler to delete the exception object when it is through with it.

Another way to create an exception object is to allocate it on the stack, as is the case with all automatic variables. When the exception is thrown, the exception object remains valid until the exception is caught and handled. Because the object is not allocated on the heap, it would be wrong to call delete on the exception object. Rather, the exception object will automatically be removed from the stack when the exception processing causes the call stack to unwind.

Another source of exception objects is static variables. Static exception objects live for the duration of the application. The exception handler should not attempt to delete a static exception object because it did not come off the heap. The exception object will remain in memory after the exception-handling logic unwinds the stack. It can be reused at other times during execution of the application.

To reiterate, the exception handler may be called with exception objects that are stack-, heap-, or static-based. The exception handler has no way to differentiate how an exception object it catches was initially allocated. MFC places the responsibility of noting how an exception was allocated on the creator of the exception object. As you saw earlier in the section that describes the CException constructor, the CException object maintains a data member called m_bAutoDelete. This flag must be set properly by the creator of the exception object. If the flag is set to TRUE, the delete operator will be called when the Delete() method is called. The m_bAutoDelete flag is defaulted to TRUE. Hence, when allocating exception objects off the heap (as MFC typically does), there is no need to specify any parameter to the constructor. If, on the other hand, you allocate a stack or static exception object, you must remember to set m_bAutoDelete by passing a FALSE to the CException() constructor.

The exception handler must cooperate with MFC by calling the exception object’s Delete() (not delete) operator when it is through processing the exception. The exception handler doesn’t need to be concerned about how an exception object was allocated. The rule is to simply call the Delete() operator in all cases and let the base class logic resolve the issue. This sounds simple enough. But there is always the proverbial “exception to the rule.”

One thing an exception handler can choose to do with an exception is to throw it (or partially process the exception and then throw it), so someone else in the exception handler stack can pick it up and continue the processing. (Kind of like a game of hot potato.) In this case, the handler should not Delete() the exception object. If Delete() was called, bad things would happen when the invalid object pointer was referenced. So remember not to Delete() an exception object unless you are really sure the application is done processing it.

Using MFC Exception Macros

When MFC first came out, C++ exceptions were still being kicked around by the standards committees. Concurrently, the general consensus (arguably) in the programming community was that Borland had a bit of a lead on MSVC 1 and MFC 1 with its IDE and OWL implementations, which at the time were quite impressive products.

For whatever reason, lack of industry direction or competitive pressure to get a product out in a timely fashion, the initial version of MFC offered exception handling only through nonstandard macros. The macros worked with CException-derived objects exclusively and were not portable. (Borland offered C++ exceptions that were very much in line with the emerging standards at that time.)

The MFC exception macros appeared similar to the emerging standards for C++ exceptions. The macros available included TRY, THROW, and CATCH. But there were differences between the C++ exceptions and the MFC macros. The CATCH macro required two parameters—one that identified the class of object the handler should process, whereas the second parameter was the pointer to the exception object to be processed. Also, multiple catch blocks were delineated by AND_CATCH and END_CATCH macros. Thus, you would get something of the form shown in Listing 22.14.



Listing 22.14 Using MFC Exception Macros


TRY
{
}
CATCH(CMemoryException, pe)
{
}
AND_CATCH(CFileException, pe)
{
}
AND_CATCH(CUserException, pe)
{
}
END_CATCH

Another difference was the THROW_LAST macro, which substituted for the C++ throw pe construct and allowed for rethrowing an exception from within a CATCH block.

MFC continued to offer the exception macros as the sole solution for exception processing until version 3. MFC version 3 provided exception handling based on C++ exceptions, which had subsequently been added to MSVC.

You could have a legacy application that uses the macro forms of exception handling. (MFC itself still uses the macro form internally.) If you find yourself dealing with the macro versions, you should seriously consider porting the logic to C++ exceptions. (You will still use the MFC CException-derived classes. You will simply do away with the MFC exception macros by replacing them with the C++ keywords.)

The steps to converting from MFC exception macros to C++ exception keywords are pretty straightforward:

1.  Replace TRY macros with the try keyword.
2.  Replace CATCH and AND_CATCH macros with the catch keyword.
3.  Change the signature of the CATCH invocation from (CException, pe) to (CException * pe).
4.  Delete the END_CATCH macros.
5.  Add calls to the Delete() method to exception handlers as required by MFC.
6.  Replace THROW_LAST macros with throw pe as appropriate.

That’s all there is to it. You will be rewarded by a smaller executable and more flexible exception handling that can detect C++ intrinsic types such as int, float, char *, and so on in addition to CException-derived objects.

Mixing Exceptions

There are limitations when mixing the various forms of exceptions discussed in this chapter. In general it’s best to avoid mixing exception mechanisms within a single method or function.

Structured exception handlers need to be isolated to separate functions. Do not mix C++ exceptions with structured exceptions in the same function. You can use both forms within the same program without a problem as long as each is segregated. There are differences in the way each exception mechanism implements stack frame processing that requires separate stack frames.

You can mix MFC macros and C++ exception syntax within an application, but as mentioned earlier, it is much better to convert the legacy MFC macro code to the newer C++-based exception syntax.

MFC exception classes can be freely mixed with intrinsic C++ variable types when using the C++ form of exception handling. This is the preferred and recommended method of handling all exceptions other than structured exceptions.

Summary

This chapter looked at three major implementations of exception handling: Win32 structured exception handling, C++ exception handling, and MFC exception handling. You have seen how exceptions can be used to trap system-level errors that would otherwise cause an application to terminate without saving state or freeing resources. You have learned how exceptions can be used to handle error conditions raised by the MFC framework. You have also looked at ways to use exceptions to simplify the handling of error conditions within application-specific logic.

All the code snippets seen in this chapter are available in their complete form as part of the CD-ROM distributed with this book. Use the examples to experiment with exceptions in order to further your knowledge.